package soulkun.library.custom.view

import android.content.Context
import android.graphics.Bitmap
import android.util.AttributeSet
import android.webkit.*

/**
 * 自定义允许与JS交互的WebView
 * 步骤：1.默认设置了WebViewClient和WebViewChromeClient，可在外部调用setWebViewClient和setWebViewChromeClient再次设置
 *      2.要实现JS调用Android方法，首先调用setInvokeAndroidMethodToJavaScript设置Android方法，再调用javaScriptInvokeAndroidMethod或getJavascriptField方法注入到JS代码中
 *      3.要实现Android调用JS方法，直接执行androidInvokeJavaScriptMethod方法即可，要额外注入JS代码片段则需要先设置setAndroidCallJavaScriptMethod方法后调用androidCallJavaScriptMethod方法
 */
class CustomWebView : WebView {

    private var customWebViewClient: CustomWebViewClient?= null
    private var customWebChromeClient: CustomWebChromeClient?= null

    var shouldOverrideUrlLoadingListener: ((WebView, WebResourceRequest) -> Boolean)? = null
    var loadFinishedOnceEnabled = true //默认只允许监听第一次完成
    var loadFinishedListener: (() -> Unit)? = null
    var errorListener: ((String) -> Unit)? = null

    private var javaScriptInvokeAndroidMethod: JavaScriptInvokeAndroidMethod? = null
    private var javaScriptInvokeAndroidMethodEnable = false
    private var androidCallJavaScriptMethod: AndroidCallJavaScriptMethod? = null
    private var androidCallJavaScriptMethodEnable = false

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    init {
        settings.run {
            javaScriptEnabled = true
            cacheMode = WebSettings.LOAD_NO_CACHE //网络加载，不需要缓存
        }
        webViewClient = WebViewClient()
        webChromeClient = WebChromeClient()
    }

    inner class CustomWebViewClient(private val webViewClient: WebViewClient): WebViewClient() {
        override fun shouldOverrideUrlLoading(
            view: WebView,
            request: WebResourceRequest
        ): Boolean {
            return if (shouldOverrideUrlLoadingListener != null) {
                shouldOverrideUrlLoadingListener!!(view, request)
            }else{
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
                    webViewClient.shouldOverrideUrlLoading(view, request)
                } else {
                    webViewClient.shouldOverrideUrlLoading(view, request.url.toString())
                }
            }
        }

        override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
            webViewClient.onPageStarted(view, url, favicon)
        }

        override fun onPageFinished(view: WebView?, url: String?) {
            if (loadFinishedOnceEnabled) {
                loadFinishedListener?.let { it() }
                loadFinishedListener = null
            }else{
                loadFinishedListener?.let { it() }
            }
            webViewClient.onPageFinished(view, url)
        }

        override fun onReceivedError(
            view: WebView?,
            errorCode: Int,
            description: String?,
            failingUrl: String?
        ) {
            errorListener?.let { it(description ?: "onReceivedError") }
            webViewClient.onReceivedError(view, errorCode, description, failingUrl)
        }
    }

    inner class CustomWebChromeClient(private val webChromeClient: WebChromeClient): WebChromeClient(){
        override fun onProgressChanged(view: WebView?, newProgress: Int) {
            webChromeClient.onProgressChanged(view, newProgress)
        }
    }

    //外部调用的设置方法
    override fun setWebViewClient(client: WebViewClient) {
        customWebViewClient = CustomWebViewClient(client)
        super.setWebViewClient(customWebViewClient!!)
    }

    //外部调用的设置方法
    override fun setWebChromeClient(client: WebChromeClient?) {
        customWebChromeClient = if (client == null) {
            CustomWebChromeClient(WebChromeClient())
        } else {
            CustomWebChromeClient(client)
        }
        super.setWebChromeClient(customWebChromeClient)
    }

    //JS端调用Android方法的接口类
    open class JavaScriptInvokeAndroidMethod(private val javaScriptInvokeAndroidMethodImpl: JavaScriptInvokeAndroidMethodImpl) {
        @JavascriptInterface
        open fun getJavaScriptField(fieldName: String, fieldValue: String) //获取Js的某个值的内容，在其中处理
        {
            javaScriptInvokeAndroidMethodImpl.getJavaScriptFieldImpl(fieldName, fieldValue)
        }
        //以下为参考方法的写法
//        @JavascriptInterface
//        open fun invokeAndroidMethod(methodName: String, vararg methodParams: String) //获取Js的某个值的内容，在其中处理
//        {
//            androidMethodToJavaScriptImpl.invokeAndroidMethod(methodName, *methodParams)
//        }
    }

    //JS端调用Android方法的代理接口
    interface JavaScriptInvokeAndroidMethodImpl{
        fun getJavaScriptFieldImpl(fieldName: String, fieldValue: String)
        //以下为参考方法的写法
//        fun invokeAndroidMethodImpl(methodName: String, vararg methodParams: String)
    }

    //设置允许JavaScript调用Android方法
    fun setJavaScriptInvokeAndroidMethod(
        enable: Boolean,
        javaScriptInvokeAndroidMethodImpl: JavaScriptInvokeAndroidMethodImpl?
    ) {
        javaScriptInvokeAndroidMethodEnable = enable
        this.javaScriptInvokeAndroidMethod = JavaScriptInvokeAndroidMethod(
            javaScriptInvokeAndroidMethodImpl ?: object : JavaScriptInvokeAndroidMethodImpl {
                override fun getJavaScriptFieldImpl(fieldName: String, fieldValue: String) {
                    println("FieldName:$fieldName, FieldValue:$fieldValue")
                }
            })
        if (javaScriptInvokeAndroidMethodEnable){
            this.addJavascriptInterface(
                this.javaScriptInvokeAndroidMethod!!,
                "JavaScriptInvokeAndroidMethod"
            )
        }
    }

    //JS调用Android方法的通用方法，返回值回调在JavaScriptInvokeAndroidMethod中设置
    fun javaScriptInvokeAndroidMethod(methodName: String, vararg methodParams: String?) {
        if (methodParams.isEmpty()) {
            loadUrl("javascript:JavaScriptInvokeAndroidMethod.$methodName()")
        }else{
            var javaScriptContent = "javascript:JavaScriptInvokeAndroidMethod.$methodName("
            for (index in methodParams.indices) {
                if (index == methodParams.size - 1) {
                    javaScriptContent += "${methodParams[index]})"
                }else{
                    javaScriptContent += "${methodParams[index]},"
                }
            }
            loadUrl(javaScriptContent)
        }
    }

    //获取JavaScript变量的封装方法，返回值回调在JavaScriptInvokeAndroidMethod中设置
    fun getJavascriptField(fieldName:String){
        loadUrl("javascript:JavaScriptInvokeAndroidMethod.getJavaScriptField('$fieldName', JSON.stringify($fieldName))")
    }

    //JS端注入Android方法的接口类
    open class AndroidCallJavaScriptMethod(private val androidCallJavaScriptMethodImpl: AndroidCallJavaScriptMethodImpl) {
        @JavascriptInterface
        open fun callJavaScriptMethod(methodName: String, methodResult: String) //调用Js方法并获取返回值
        {
            androidCallJavaScriptMethodImpl.callJavaScriptMethodImpl(methodName, methodResult)
        }
        @JavascriptInterface
        open fun catchJavaScriptMethodError(methodName: String, error: String) //调用Js方法时捕捉异常
        {
            androidCallJavaScriptMethodImpl.catchJavaScriptMethodErrorImpl(methodName, error)
        }
    }

    //JS端调用Android方法的代理接口
    interface AndroidCallJavaScriptMethodImpl{
        fun callJavaScriptMethodImpl(methodName: String, methodResult: String)
        fun catchJavaScriptMethodErrorImpl(methodName: String, error: String)
    }

    //设置允许Android注入JS代码
    fun setAndroidCallJavaScriptMethod(
        enable: Boolean,
        androidCallJavaScriptMethodImpl: AndroidCallJavaScriptMethodImpl?
    ) {
        androidCallJavaScriptMethodEnable = enable
        this.androidCallJavaScriptMethod = AndroidCallJavaScriptMethod(
            androidCallJavaScriptMethodImpl ?: object : AndroidCallJavaScriptMethodImpl {
                override fun callJavaScriptMethodImpl(methodName: String, methodResult: String) {
                    println("MethodName:$methodName, MethodResult:$methodResult")
                }

                override fun catchJavaScriptMethodErrorImpl(methodName: String, error: String) {
                    println("MethodName:$methodName, Error:$error")
                }
            })
        if (androidCallJavaScriptMethodEnable){
            this.addJavascriptInterface(
                this.androidCallJavaScriptMethod!!,
                "AndroidCallJavaScriptMethod"
            )
        }
    }

    //Android调用JS方法的通用方法（用于返回String值），并获取返回值String后执行回调方法
    //注意，要是方法返回另一个方法，需要多个括号！
    fun androidInvokeJavaScriptMethod(methodName: String, vararg methodParams: String?, resultCallback: ValueCallback<String>){
        if (methodParams.isEmpty()) {
            evaluateJavascript("javascript:$methodName()", resultCallback)
        }else{
            var javaScriptContent = "javascript:$methodName("
            for (index in methodParams.indices) {
                if (index == methodParams.size - 1) {
                    javaScriptContent += "${methodParams[index]})"
                }else{
                    javaScriptContent += "${methodParams[index]},"
                }
            }
            evaluateJavascript(javaScriptContent, resultCallback)
        }
    }

    //Android注入JS代码片段的通用方法（用于在JS中调用JS方法），并在JS代码中处理逻辑，返回值回调在JavaScriptInvokeAndroidMethod中设置
    fun androidCallCustomJavaScriptMethod(customJavaScriptMethod: String) {
        loadUrl(customJavaScriptMethod)
    }

    //Android注入JS代码以获取JS方法的返回值
    fun getJavaScriptMethodResult(methodName: String, vararg methodParams: String?) {
        if (methodParams.isEmpty()) {
            loadUrl("""javascript:(function(){
                                try{
                                    AndroidCallJavaScriptMethod.callJavaScriptMethod('$methodName()', $methodName());
                                }catch(error) {
                                    AndroidCallJavaScriptMethod.catchJavaScriptMethodError('$methodName()', error);
                                }
                           })();
                     """)
        } else {
            var javaScriptMethod = "$methodName("
            for (index in methodParams.indices) {
                if (index == methodParams.size - 1) {
                    javaScriptMethod += "${methodParams[index]})"
                }else{
                    javaScriptMethod += "${methodParams[index]},"
                }
            }
            loadUrl(
                """javascript:(function(){
                            try{
                                AndroidCallJavaScriptMethod.callJavaScriptMethod('$javaScriptMethod', $javaScriptMethod);
                            }catch(error) {
                                AndroidCallJavaScriptMethod.catchJavaScriptMethodError('$javaScriptMethod', error);
                            }
                        })();
                     """
            )
        }
    }

    //Android注入JS代码以获取JS异步方法的返回值
    fun getAsyncJavaScriptMethodResult(methodName: String, vararg methodParams: String?) {
        if (methodParams.isEmpty()) {
            loadUrl(
                """javascript:(function(){
                                $methodName().then(function (result) {
                                    AndroidCallJavaScriptMethod.callJavaScriptMethod('$methodName()', JSON.stringify(result));
                                }).catch(function (error) {
                                    AndroidCallJavaScriptMethod.catchJavaScriptMethodError('$methodName()', error);
                              });
                           })();
                     """
            )
        } else {
            var javaScriptMethod = "$methodName("
            for (index in methodParams.indices) {
                if (index == methodParams.size - 1) {
                    javaScriptMethod += "${methodParams[index]})"
                }else{
                    javaScriptMethod += "${methodParams[index]},"
                }
            }
            loadUrl("""javascript:(function(){
                                $javaScriptMethod.then(function (result) {
                                    AndroidCallJavaScriptMethod.callJavaScriptMethod('$javaScriptMethod', JSON.stringify(result));
                                }).catch(function (error) {
                                    AndroidCallJavaScriptMethod.catchJavaScriptMethodError('$javaScriptMethod', error);
                              });
                        })();
                     """)
        }
    }


}